;; The first three lines of this file were inserted by DrRacket. They record metadata
;; about the language level of this file in a form that our tools can easily process.
#reader(lib "htdp-intermediate-lambda-reader.ss" "lang")((modname |11|) (read-case-sensitive #t) (teachpacks ()) (htdp-settings #(#t constructor repeating-decimal #f #t none #f ())))
(require 2htdp/image)
(require rackunit)

;; Testing done on Linux (Ubuntu) , resolution 1440 x 900

;; ALGORITHM
;; 1. Given an expression and check if it is number or a difference or 
;;    multiplication expression
;; 2. Check if the expression if placed in a horizontal line can fit into 
;;    the given width.
;; 3. If the expression fits then render the expression in a single line
;; 4. If the expression does not fit and 
;;    a) the expression is either a difference or multiplication expression 
;;       then render the expression in stacked fashion and apply steps
;;       1 to 4 on each subexpression, if any.
;;    b) the expression is a number then raise an error saying that there is 
;;       not enough room.

;;;;;;;;;;;;;;;;;;;;;;;;;;Start of Data Definitions;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(define-struct diff-exp (exprs))
;; a diff-exp is (make-diff-exp NELOExpr)
;; Interpretation:
;; exprs : is a non empty list of Exprs
;;
;; Template
;; diff-exp-fn : diff-exp -> ??
;(define (diff-exp-fn exp)
;  (...(neloexpr-fn (diff-exp-exprs exp))))

(define-struct mult-exp (exprs))
;; a mult-expr is (make-mult-exp NELOExpr)
;; Interpretation:
;; exprs : is a non empty list of Exprs
;;
;; Template
;; mult-exp-fn : mult-exp -> ??
;(define (mult-exp-fn exp)
;  (... (neloexpr-fn (mult-exp-exprs exp))))

;; An Expr is one of
;; -- Number
;;       Interp: A number
;; -- (make-diff-exp NELOExpr)
;;       Interp: A difference expression
;; -- (make-mult-exp NELOExpr)
;;       Interp: A multiplication expression
;;
;; Template
;; expr-fn : Expr -> ??
;(define (expr-fn exp)
;  (cond
;    [(number? exp) ...]
;    [(diff-exp? exp) ...(diff-exp-fn exp)]
;    [(mult-exp? exp) ...(mult-exp-fn exp)]))

;; A Non-Empty List of Exprs (NELOExpr) is one of
;; -- (cons Expr empty)
;;       Interp: A list of single Expr
;; -- (cons Expr NELOExpr)
;;       Interp: A list of more than one Expr
;;
;; Template
;; neloexpr-fn : NELOExpr -> ??
;(define (neloexpr-fn loexpr)
;  (cond
;    [(empty? (rest loexpr)) ...(expr-fn (first loexpr))]
;    [else ...(expr-fn (first loexpr))
;          ...(neloexpr-fn (rest neloexpr))]))

;; Template with accumulator:
;; expr-fn : Expr -> Z
;(define (expr-fn exp0)
;  (local
;    (;; expr-fn-in-context : Expr Y -> Z
;     ;; INVARIANT: explain the relationship between exp0, exp and acc. 
;     ;; short purpose statement
;     (define (expr-fn-in-context exp acc)
;       (cond
;         [(number? exp) ...]
;         [(diff-exp? exp) ...(diff-exp-fn exp acc)]
;         [(mult-exp? exp) ...(mult-exp-fn exp acc)]))
;     
;     (define (neloexpr-fn loexpr acc)
;       (cond
;         [(empty? (rest loexpr)) ...(expr-fn-in-context (first loexpr) ... acc)]
;         [else ...(expr-fn-in-context (first loexpr) ... acc)
;               ...(neloexpr-fn (rest neloexpr) acc)]))
;     
;     (define (diff-exp-fn exp acc)
;       (...(neloexpr-fn (diff-exp-exprs exp) acc)))
;     
;     (define (mult-exp-fn exp acc)
;       (... (neloexpr-fn (mult-exp-exprs exp) acc))))
;    (expr-fn-in-context expr0 ...)))

;;;;;;;;;;;;;;;;;;;;;;;;;;End of Data Definitions;;;;;;;;;;;;;

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;Constants;;;;;;;;;;;;;;;;;;;;;;;;

;; Font size
(define FONT-SIZE 11)

;; FONT-COLOR
(define FONT-COLOR "black")

;; image of "(- " 
(define DIFF-IMG (text "(- " 11 "black"))

;; image of "(* "
(define MULT-IMG (text "(* " 11 "black"))

;; image of ")"
(define CLOSE-BRAC (text ")" 11 "black"))

;; image of " "
(define SPACE-IMG (text " " 11 "black"))

;; image width of " "
(define SPACE-IMG-WIDTH (image-width SPACE-IMG))

;; image height of a single line.
;; used for testing
(define line-height (image-height (text " " 11 "black")))

(define ZERO 0)

(define MINUS "-")

(define MULT  "*")
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; multi-line-height : Number -> Number
;; takes the number of lines and computes the total image height
;; of the given number of lines.
;; used for testing
;; Examples : if line-height = 10 
;; then (multi-line-height 2) = 2 * 10 = 20
;; Strategy: Domain Knowedge
(define (multi-line-height n)
  (* n line-height))

;; number->image : Number Number -> Image
;; given a number and the number of brackets, returns an image with
;; closing brackets appended to the number
;; Examples : See tests below
;; Strategy : Function Compostion
(define (number->image num n-brackets)
  (text (string-append (number->string num) (make-string n-brackets #\))) FONT-SIZE FONT-COLOR))

;; is-room-sufficient? : Image Number -> Boolean
;; given an image and the lateral width, checks if the image can fit within 
;; the width
;; Examples : See tests below
;; Strategy : Function composition
(define (is-room-sufficient? img room)
  (<= (image-width img) room))

;; render : Expr Number -> Image
;; given expression is rendered on a single line if it fits within the 
;; specified width. Otherwise, the subexpressions are rendered in a 
;; stacked fashion. If all subexpressions fit within the space allotted
;; minus the space for surrounding parentheses, if any, then subexpressions
;; are rendered, else an error will be raised
;; Examples : See tests below
;; Strategy : Function Composition
(define (render exp0 room0)
  (render-beside exp0 room0 ZERO))

;; render-beside : Expr Number -> Image
;; given an expression, room and number of brackets, the expression is rendered
;; on a single line if it fits within the specified width.
;; Examples : See tests below
;; Strategy : Strutural Decompostion [Expr] with accumulators [room] and 
;; [n-brackets]
(define (render-beside exp0 room0 n-brackets0)
  (local (; render-in-context : Expr Number Number -> Image
          ; INVARIANT : given exp0 = 's1[a b 's2[c d 's3[e f ...'sk[x y z]]]...]
          ;             when exp = z, then n-brackets = k 
          ;             where (s1, s2...sk are nested diff/mult-exp;
          ;             a,b...z can be number or diff/mult-exp)
          ; INVARIANT : room = room0 - image-width (exp0 - exp)
          ;                          - image width of all prior
          ;                            (opening-brac + sign + space)
          ;                            where sign can be "-" or "*";
          ;                          - n-brackets * image-width(closing bracket)
          ;             where exp is a subset of exp0
          ; PURPOSE : renders the expression in a 
          ; single line if there is enough room
          (define (render-in-context exp room n-brackets)
            (cond
              [(number? exp) 
               (if (is-room-sufficient? (number->image exp ZERO) room)
                   (number->image exp n-brackets)
                   (render-above exp room n-brackets))]
              [(diff-exp? exp) 
               (render-subexp-fold 
                (diff-exp-exprs exp) DIFF-IMG "-" room n-brackets)]
              [(mult-exp? exp) 
               (render-subexp-fold 
                (mult-exp-exprs exp) MULT-IMG "*" room n-brackets)]))
          
          ; is-room-sufficient-for-exp? : Expr String Number -> Boolean
          ; Checks if Expr can fit within the given width, this Expr can
          ; be a Diff or a Mult Expr
          (define (is-room-sufficient-for-exp? exp sign room)
            (local (; image of opening-sign-closing parentheses
                    (define img 
                      (text (string-append "(" sign " )") FONT-SIZE FONT-COLOR)))
              (if (is-room-sufficient? 
                   img
                   (sub1 room))
                  (is-room-sufficient-for-neloexpr?
                   exp
                   (- room 
                      (image-width 
                       img)))
                  false)))
          
          ; is-room-sufficient-for-neloexpr? : NELOExpr Number -> Boolean
          ; Checks if given NELOExpr can fit within the given width
          (define (is-room-sufficient-for-neloexpr? loexpr room)
            (cond
              [(empty? (rest loexpr)) 
               (can-display-horizontally? (first loexpr) room)]
              [else (if (can-display-horizontally? 
                         (first loexpr) (- room SPACE-IMG-WIDTH))
                        (is-room-sufficient-for-neloexpr?
                         (rest loexpr) 
                         (- room 
                            SPACE-IMG-WIDTH
                            (image-width 
                             (render-in-context (first loexpr) room ZERO))))
                        false)]))
          
          ; can-display-horizontally? : Expr Number -> Boolean
          ; Checks if Expr can fit within the given width
          (define (can-display-horizontally? exp room)
            (cond
              [(number? exp) (is-room-sufficient? (number->image exp ZERO) room)]
              [(diff-exp? exp) 
               (is-room-sufficient-for-exp? (diff-exp-exprs exp) "-" room)]
              [(mult-exp? exp) 
               (is-room-sufficient-for-exp? (mult-exp-exprs exp) "*" room)]))
          
          
          ; place-beside-loexpr : NELOExpr Number Number -> Image
          ; renders NELOExpr in a single line
          (define (place-beside-loexpr loexpr room n-brackets)
            (cond
              [(empty? (rest loexpr))
               (render-in-context 
                (first loexpr) 
                (- room (image-width CLOSE-BRAC))
                (add1 n-brackets))]
              [else 
               (local (; rendered (first loexpr) image
                       (define img 
                         (render-in-context 
                          (first loexpr) 
                          (- room SPACE-IMG-WIDTH)
                          ZERO)))
                 (beside
                  (beside/align "top" img SPACE-IMG)
                  (place-beside-loexpr 
                   (rest loexpr) 
                   (- room SPACE-IMG-WIDTH 
                      (image-width img)) n-brackets)))]))
          
          ; render-subexp-fold : NELOExpr Image String Number Number -> Image
          ; renders the given diff or mult exp, in a single line if there is
          ; enough room else renders in a stacked fashion
          (define (render-subexp-fold loexpr img sign room n-brackets)
            (if (is-room-sufficient-for-exp? loexpr sign room)
                (beside/align 
                 "top"
                 img
                 (place-beside-loexpr 
                  loexpr (- room (image-width img)) n-brackets))
                (render-above exp0 room0 n-brackets0))))
    (render-in-context exp0 room0 n-brackets0)))


;; render-above : Expr Number Number -> Image
;; given an expression, room and number of brackets, renders the expression
;; in a stacked fashion if it fits within the specified width. 
;; Otherwise, an error is raised be raised
;; Examples : See tests below
;; Strategy : Strutural Decompostion [Expr] with accumulators [room] and 
;; [n-brackets]
(define (render-above exp0 room0 n-brackets0)
  (local (; render-in-context : Expr Number Number -> Image
          ; INVARIANT : given exp0 = 's1[a b 's2[c d 's3[e f ...'sk[x y z]]]...]
          ;             when exp = z, then n-brackets = k 
          ;             where (s1, s2...sk are nested diff/mult-exp;
          ;             a,b...z can be number or diff/mult-exp)
          ; INVARIANT : room = room0 
          ;                    - (image width of all outer
          ;                       (opening-brac + sign + space)
          ;                       where sign can be "-" or "*"
          ;                    - n-brackets * image-width (closing bracket)
          ; where loexpr is a subset of loexpr0
          ; PURPOSE: given an expression and available room and 
          ; number of brackets, renders the expression in a stacked fashion
          ; if there is enough room
          (define (render-in-context exp room n-brackets)
            (cond
              [(number? exp) (error "not enough room")]
              [(diff-exp? exp) 
               (render-subexp-fold 
                (diff-exp-exprs exp) DIFF-IMG MINUS room n-brackets)]
              [(mult-exp? exp) 
               (render-subexp-fold 
                (mult-exp-exprs exp) MULT-IMG MULT room n-brackets)]))
          
          ; place-above-loexpr : NELOExpr Number Number -> Image
          ; renders NELOExpr in a stacked fashion
          (define (place-above-loexpr loexpr room n-brackets)
            (cond
              [(empty? (rest loexpr))
               (render-beside (first loexpr) 
                              (- room (image-width CLOSE-BRAC))
                              (add1 n-brackets))]
              [else 
               (above/align "left"
                            (render-beside (first loexpr) room ZERO)
                            (place-above-loexpr (rest loexpr) 
                                                room n-brackets))]))
          
          ; render-subexp-fold : NELOExpr Image String Number Number -> Image
          ; renders the given diff or mult exp, in a stacked fashion
          (define (render-subexp-fold loexpr img sign room n-brackets)
            (beside/align
             "top"
             img
             (place-above-loexpr loexpr 
                                 (- room (image-width img)) n-brackets))))
    (render-in-context exp0 room0 n-brackets0)))

;;;;;;;;;;;;;;;;;;;;;;;;;tests for render;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; an example of mult-level nested diff and mult expressions
(define example 
  (make-diff-exp 
   (list 22 33 (make-diff-exp 
                (list 4444444444 55 
                      (make-mult-exp 
                       (list 3
                             4 
                             (make-diff-exp
                              (list 111 222))
                             666666
                             (make-diff-exp 
                              (list 55 3333))
                             7 
                             8888888888
                             (make-mult-exp
                              (list 3 4 (make-mult-exp (list 555 1))))
                             9
                             (make-mult-exp
                              (list 3 44)))))))))

; test for 1 number that fits within the given width
(check-equal? 
 (image-width (render 345 40))
 24
 "this image should be rendered in a single line")

; test for 1 number that does not fits within the given width
(check-error 
 (image-width (render 345 20))
 "not enough room")
 
; test for 1 diff-exp that fits in the given width
(check-equal? 
 (image-width (render (make-diff-exp (list 12)) 40))
 35
 "this image should be rendered in a single line")

; test for 1 mult-exp that fits in the given width
(check-equal? 
 (image-width (render (make-mult-exp (list 12)) 40))
 36
 "this image should be rendered in a single line")

; test for 1 diff-exp that does not fit in the given width
(check-error 
 (render (make-diff-exp (list 12)) 20)
 "not enough room")

; test for 1 mult-exp that does not fit in the given width
(check-error 
 (render (make-mult-exp (list 12)) 20)
 "not enough room")

; test for 1 diff-exp with 2 numbers that fits in the given width
(check-equal? 
 (image-width (render (make-diff-exp (list 12 20)) 60))
 55
 "this image should be rendered in a single line")

;test for 1 diff-exp with 2 numbers that cannot be rendered at all
(check-error 
 (render (make-diff-exp (list 12 20)) 30)
 "not enough room")

; test for 1 diff-exp with 2 numbers that cannot be rendered in a single line
(check-equal? 
 (image-height (render (make-diff-exp (list 12 20)) 50))
 (multi-line-height 2)
 "this image should be rendered in 2 lines")

; test for 1 diff-exp with 2 numbers that cannot be rendered in a single line
(check-equal? 
 (image-width (render (make-diff-exp (list 12 20)) 50))
 35
 "this image should be rendered in 2 lines")

;test for 1 mult-exp with 2 numbers that cannot be rendered at all
(check-error 
 (render (make-mult-exp (list 12 20)) 30)
 "not enough room")

; test for 1 mult-exp with 2 numbers that cannot be rendered in a single line
(check-equal? 
 (image-height (render (make-mult-exp (list 12 20)) 50))
 (multi-line-height 2)
 "this image should be rendered in 2 lines")

; test for 1 mult-exp with 2 numbers that cannot be rendered in a single line
(check-equal? 
 (image-width (render (make-mult-exp (list 12 20)) 50))
 36
 "this image should be rendered in 2 lines")



; test for 1 diff-exp within 1 mult-exp that can be rendered in a single line
(check-equal? 
 (image-width (render (make-mult-exp (list 12 (make-diff-exp (list 23)))) 80))
 75
 "this image should be rendered in a single line")

; test for 1 diff-exp within 1 mult-exp that will be rendered in a stacked fashion
(check-equal? 
 (image-height (render (make-mult-exp (list 12 (make-diff-exp (list 23)))) 70))
 (multi-line-height 2)
 "this image should be rendered in 2 lines")

; test for 1 diff-exp within 1 mult-exp that cannot be rendered at all
(check-error 
 (render (make-mult-exp (list 12 (make-diff-exp (list 23)))) 50)
 "not enough room")

; test for 1 mult-exp within 1 diff-exp that can be rendered in a single line
(check-equal? 
 (image-width (render (make-diff-exp (list 12 (make-mult-exp (list 23)))) 80))
 75
 "this image should be rendered in a single line")

; test for 1 mult-exp within 1 diff-exp that will be rendered in a stacked fashion
(check-equal? 
 (image-height (render (make-diff-exp (list 12 (make-mult-exp (list 23)))) 70))
 (multi-line-height 2)
 "this image should be rendered in 2 lines")

; test for 1 mult-exp within 1 diff-exp that cannot be rendered at all
(check-error 
 (render (make-diff-exp (list 12 (make-mult-exp (list 23)))) 50)
 "not enough room")

; test for rendering nested diff-exp and mult-exp in a single line
; with all closing brackets at the end
(check-equal?
 (render (make-mult-exp (list 12 (make-diff-exp (list 23)))) 80)
 (text "(* 12 (- 23))" 11 FONT-COLOR)
 "this image should be rendered on a single line with two closing brackets at
 the end")

; test for rendering nested diff-exp and mult-exp in a single line
; with only the outermost closing bracket at the end
(check-equal?
 (render (make-mult-exp (list (make-diff-exp (list 23)) 12)) 80)
 (text "(* (- 23) 12)" 11 "black")
 "this image should be rendered in a single line with one closing bracket at
 the end")

; test for rendering of multi-level nested diff and mult expressions on a 
; single line
(check-equal? 
 (image-width (render example 700))
 688
 "this image should be rendered on a single line")

; test for rendering of multi-level nested diff and mult expressions in a 
; stacked fashion
(check-equal? 
 (image-height (render example 200))
 (multi-line-height 14)
 "this image should be rendered in 14 lines")

; test for multi-level nested diff and mult expressions not being rendered at all
(check-error 
 (render example 50)
 "not enough room")

; test for placing of closing brackets after an expression
(check-equal?
 (render (make-diff-exp (list 234 1)) 40)
 (beside/align "top" 
               (text "(- " 11 "black")
               (above/align "left" 
                            (text "234" 11 "black")
                            (text "1)" 11 "black")))
 "there should be no space between the last element and the closing
  bracket")
